// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;

namespace ILCompiler.DependencyAnalysis
{
    public enum RelocType
    {
        // PE base relocation types.
        IMAGE_REL_BASED_ABSOLUTE             = 0x00,   // No relocation required
        IMAGE_REL_BASED_HIGHLOW              = 0x03,   // 32 bit address base
        IMAGE_REL_BASED_THUMB_MOV32          = 0x07,   // Thumb2: based MOVW/MOVT
        IMAGE_REL_BASED_DIR64                = 0x0A,   // 64 bit address base

        // COFF relocation types
        IMAGE_REL_BASED_ADDR32NB             = 0x0B,   // The 32-bit address without an image base (RVA)

        // General relocation types
        IMAGE_REL_BASED_REL32                = 0x10,   // 32-bit relative address from byte following reloc
        IMAGE_REL_BASED_THUMB_BRANCH24       = 0x13,   // Thumb2: based B, BL
        IMAGE_REL_BASED_THUMB_MOV32_PCREL    = 0x14,   // Thumb2: based MOVW/MOVT
        IMAGE_REL_BASED_ARM64_BRANCH26       = 0x15,   // Arm64: B, BL
        IMAGE_REL_BASED_LOONGARCH64_PC       = 0x16,   // LoongArch64: pcalau12i+imm12
        IMAGE_REL_BASED_LOONGARCH64_JIR      = 0x17,   // LoongArch64: pcaddu18i+jirl
        IMAGE_REL_BASED_RISCV64_CALL_PLT     = 0x18,   // RiscV64: auipc + jalr
        IMAGE_REL_BASED_RISCV64_PCREL_I      = 0x19,   // RiscV64: auipc + I-type
        IMAGE_REL_BASED_RISCV64_PCREL_S      = 0x20,   // RiscV64: auipc + S-type
        IMAGE_REL_BASED_RELPTR32             = 0x7C,   // 32-bit relative address from byte starting reloc
                                                       // This is a special NGEN-specific relocation type
                                                       // for relative pointer (used to make NGen relocation
                                                       // section smaller)

        IMAGE_REL_SECTION                    = 0x79,   // 16 bit section index containing target

        IMAGE_REL_BASED_ARM64_PAGEBASE_REL21 = 0x81,   // ADRP
        IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A = 0x82,   // ADD/ADDS (immediate) with zero shift, for page offset
        IMAGE_REL_BASED_ARM64_PAGEOFFSET_12L = 0x83,   // LDR (indexed, unsigned immediate), for page offset

        //
        // Relocation operators related to TLS access
        //

        // Windows x64
        IMAGE_REL_SECREL                     = 0x104,

        // Linux x64
        // GD model
        IMAGE_REL_TLSGD                      = 0x105,
        // LE model
        IMAGE_REL_TPOFF                      = 0x106,

        // Linux arm64
        //    TLSDESC  (dynamic)
        IMAGE_REL_AARCH64_TLSDESC_ADR_PAGE21 = 0x107,
        IMAGE_REL_AARCH64_TLSDESC_LD64_LO12  = 0x108,
        IMAGE_REL_AARCH64_TLSDESC_ADD_LO12   = 0x109,
        IMAGE_REL_AARCH64_TLSDESC_CALL       = 0x10A,
        //    LE model
        IMAGE_REL_AARCH64_TLSLE_ADD_TPREL_HI12    = 0x10B,
        IMAGE_REL_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 0x10C,

        // Linux arm32
        IMAGE_REL_ARM_PREL31                 = 0x10D,
        IMAGE_REL_ARM_JUMP24                 = 0x10E,

        // Windows arm64 TLS access
        IMAGE_REL_ARM64_TLS_SECREL_HIGH12A = 0x10F,  // ADD high 12-bit offset for tls
        IMAGE_REL_ARM64_TLS_SECREL_LOW12A  = 0x110,  // ADD low 12-bit offset for tls

        //
        // Relocations for R2R image production
        // None of these are "real" relocations that map to an object file's relocation.
        // All must be emulated by the object writer.
        //
        IMAGE_REL_SYMBOL_SIZE                = 0x1000, // The size of data in the image represented by the target symbol node
        IMAGE_REL_FILE_ABSOLUTE              = 0x1001, // 32 bit offset from beginning of image
        IMAGE_REL_FILE_CHECKSUM_CALLBACK     = 0x1002, // After the image has been emitted, call the IChecksumNode.EmitChecksum method on the target symbol to emit the checksum data.
    }

    public struct Relocation
    {
        public readonly RelocType RelocType;
        public readonly int Offset;
        public readonly ISymbolNode Target;

        //*****************************************************************************
        //  Extract the 16-bit immediate from ARM Thumb2 Instruction (format T2_N)
        //*****************************************************************************
        private static unsafe ushort GetThumb2Imm16(ushort* p)
        {
            uint Opcode0 = (uint)p[0];
            uint Opcode1 = (uint)p[1];
            uint Result =
                ((Opcode0 << 12) & 0xf000) |
                ((Opcode0 <<  1) & 0x0800) |
                ((Opcode1 >>  4) & 0x0700) |
                ((Opcode1 >>  0) & 0x00ff);
            return (ushort)Result;
        }

        //*****************************************************************************
        //  Deposit the 16-bit immediate into ARM Thumb2 Instruction (format T2_N)
        //*****************************************************************************
        private static unsafe void PutThumb2Imm16(ushort* p, ushort imm16)
        {
            uint Opcode0 = (uint)p[0];
            uint Opcode1 = (uint)p[1];
            int val0 = (0xf000 >> 12);
            int val1 = (0x0800 >>  1);
            Opcode0 &= ~((uint)val0 | (uint)val1);
            int val3 = (0x0700 <<  4);
            Opcode1 &= ~((uint)val3 | (0x00ff << 0));
            Opcode0 |= ((uint)imm16 & 0xf000) >> 12;
            Opcode0 |= ((uint)imm16 & 0x0800) >>  1;
            Opcode1 |= ((uint)imm16 & 0x0700) <<  4;
            Opcode1 |= ((uint)imm16 & 0x00ff) <<  0;
            p[0] = (ushort)Opcode0;
            p[1] = (ushort)Opcode1;
        }

        //*****************************************************************************
        //  Extract the 32-bit immediate from movw/movt sequence
        //*****************************************************************************
        private static unsafe int GetThumb2Mov32(ushort* p)
        {
            // Make sure we are decoding movw/movt sequence
            ushort Opcode0 = *(p + 0);
            ushort Opcode1 = *(p + 2);
            Debug.Assert(((uint)Opcode0 & 0xFBF0) == 0xF240);
            Debug.Assert(((uint)Opcode1 & 0xFBF0) == 0xF2C0);

            return (int)GetThumb2Imm16(p) + ((int)(GetThumb2Imm16(p + 2) << 16));
        }

        //*****************************************************************************
        //  Deposit the 32-bit immediate into movw/movt Thumb2 sequence
        //*****************************************************************************
        private static unsafe void PutThumb2Mov32(ushort* p, uint imm32)
        {
            // Make sure we are decoding movw/movt sequence
            ushort Opcode0 = *(p + 0);
            ushort Opcode1 = *(p + 2);
            Debug.Assert(((uint)Opcode0 & 0xFBF0) == 0xF240);
            Debug.Assert(((uint)Opcode1 & 0xFBF0) == 0xF2C0);

            ushort imm16 = (ushort)(imm32 & 0xffff);
            PutThumb2Imm16(p, imm16);
            imm16 = (ushort)(imm32 >> 16);
            PutThumb2Imm16(p + 2, imm16);

            Debug.Assert((uint)GetThumb2Mov32(p) == imm32);
        }

        //*****************************************************************************
        //  Extract the 24-bit rel offset from bl instruction
        //*****************************************************************************
        private static unsafe int GetThumb2BlRel24(ushort* p)
        {
            uint Opcode0 = (uint)p[0];
            uint Opcode1 = (uint)p[1];

            uint S  = Opcode0 >> 10;
            uint J2 = Opcode1 >> 11;
            uint J1 = Opcode1 >> 13;

            uint ret =
                ((S << 24)              & 0x1000000) |
                (((J1 ^ S ^ 1) << 23)   & 0x0800000) |
                (((J2 ^ S ^ 1) << 22)   & 0x0400000) |
                ((Opcode0 << 12)        & 0x03FF000) |
                ((Opcode1 <<  1)        & 0x0000FFE);

            // Sign-extend and return
            return (int)(ret << 7) >> 7;
        }

        //*****************************************************************************
        // Returns whether the offset fits into bl instruction
        //*****************************************************************************
        public static bool FitsInThumb2BlRel24(int imm24)
        {
            return ((imm24 << 7) >> 7) == imm24;
        }

        //*****************************************************************************
        //  Deposit the 24-bit rel offset into bl instruction
        //*****************************************************************************
        private static unsafe void PutThumb2BlRel24(ushort* p, int imm24)
        {
            // Verify that we got a valid offset
            Debug.Assert(FitsInThumb2BlRel24(imm24));

            // Ensure that the ThumbBit is not set on the offset
            // as it cannot be encoded.
            Debug.Assert((imm24 & 1/*THUMB_CODE*/) == 0);

            uint Opcode0 = (uint)p[0];
            uint Opcode1 = (uint)p[1];
            Opcode0 &= 0xF800;
            Opcode1 &= 0xD000;

            uint S  =  ((uint)imm24 & 0x1000000) >> 24;
            uint J1 = (((uint)imm24 & 0x0800000) >> 23) ^ S ^ 1;
            uint J2 = (((uint)imm24 & 0x0400000) >> 22) ^ S ^ 1;

            Opcode0 |=  (((uint)imm24 & 0x03FF000) >> 12) | (S << 10);
            Opcode1 |=  (((uint)imm24 & 0x0000FFE) >>  1) | (J1 << 13) | (J2 << 11);

            p[0] = (ushort)Opcode0;
            p[1] = (ushort)Opcode1;

            Debug.Assert(GetThumb2BlRel24(p) == imm24);
        }

        //*****************************************************************************
        //  Extract the PC-Relative offset from an adrp instruction
        //*****************************************************************************
        private static unsafe int GetArm64Rel21(uint* pCode)
        {
            int adrpInstr = (int)*pCode;

            // 23-5 bits for the high part. Shift it by 5.
            int immhi = (adrpInstr & 0xFFFFE0) >> 5;
            // 30,29 bits for the lower part. Shift it by 29.
            int immlo = (adrpInstr & 0x60000000) >> 29;

            // Merge them
            int imm21 = (immhi << 2) | immlo;

            return imm21;
        }

        //*****************************************************************************
        // Returns whether the offset fits into an Arm64 adrp instruction
        //*****************************************************************************
        private static bool FitsInRel21(int val32)
        {
            return (val32 >= 0) && (val32 <= 0x001FFFFF);
        }

        //*****************************************************************************
        //  Deposit the PC-Relative offset 'imm21' into an adrp instruction
        //*****************************************************************************
        private static unsafe void PutArm64Rel21(uint* pCode, int imm21)
        {
            // Verify that we got a valid offset
            Debug.Assert(FitsInRel21(imm21));

            uint adrpInstr = *pCode;
            // Check adrp opcode 1ii1 0000 ...
            Debug.Assert((adrpInstr & 0x9F000000) == 0x90000000);

            adrpInstr &= 0x9F00001F;             // keep bits 31, 28-24, 4-0.
            int immlo = imm21 & 0x03;            // Extract low 2 bits which will occupy 30-29 bits.
            int immhi = (imm21 & 0x1FFFFC) >> 2; // Extract high 19 bits which will occupy 23-5 bits.
            adrpInstr |= (uint)((immlo << 29) | (immhi << 5));

            *pCode = adrpInstr;                  // write the assembled instruction

            Debug.Assert(GetArm64Rel21(pCode) == imm21);
        }

        //*****************************************************************************
        //  Extract the PC-Relative offset from an add instruction
        //*****************************************************************************
        private static unsafe int GetArm64Rel12(uint* pCode)
        {
            uint addInstr = *pCode;

            // 21-10 contains value. Mask 12 bits and shift by 10 bits.
            int imm12 = (int)(addInstr & 0x003FFC00) >> 10;

            return imm12;
        }

        //*****************************************************************************
        // Returns whether the offset fits into an Arm64 add instruction
        //*****************************************************************************
        private static bool FitsInRel12(int val32)
        {
            return (val32 >= 0) && (val32 <= 0x00000FFF);
        }

        //*****************************************************************************
        //  Deposit the PC-Relative offset 'imm12' into an add instruction
        //*****************************************************************************
        private static unsafe void PutArm64Rel12(uint* pCode, int imm12)
        {
            // Verify that we got a valid offset
            Debug.Assert(FitsInRel12(imm12));

            uint addInstr = *pCode;
            // Check add opcode 1001 0001 00...
            Debug.Assert((addInstr & 0xFFC00000) == 0x91000000);

            addInstr &= 0xFFC003FF;          // keep bits 31-22, 9-0
            addInstr |= (uint)(imm12 << 10); // Occupy 21-10.

            *pCode = addInstr;               // write the assembled instruction

            Debug.Assert(GetArm64Rel12(pCode) == imm12);
        }

        //*****************************************************************************
        //  Deposit the PC-Relative offset 'imm12' into an add instruction
        //  Same as PutArm64Rel12(), except the assert here checks if "LSL #3" is encoded
        //  in the instruction.
        //*****************************************************************************
        private static unsafe void PutArm64TlsRel12(uint* pCode, int imm12)
        {
            // Verify that we got a valid offset
            Debug.Assert(FitsInRel12(imm12));

            uint addInstr = *pCode;
            // Check add opcode 1001 0001 00...
            Debug.Assert((addInstr & 0xFFC00000) == 0x91400000);

            addInstr &= 0xFFC003FF;          // keep bits 31-22, 9-0
            addInstr |= (uint)(imm12 << 10); // Occupy 21-10.

            *pCode = addInstr;               // write the assembled instruction

            Debug.Assert(GetArm64Rel12(pCode) == imm12);
        }

        //*****************************************************************************
        //  Extract the 12-bit page offset from an LDR instruction (unsigned immediate)
        //  For 64-bit LDR, the immediate is scaled by 8 bytes
        //*****************************************************************************
        private static unsafe int GetArm64Rel12Ldr(uint* pCode)
        {
            uint ldrInstr = *pCode;

            // 0x003FFC00: Mask for bits 21-10 of the 32-bit ARM64 LDR instruction
            // which contain the scaled immediate value
            int scaledImm12 = (int)(ldrInstr & 0x003FFC00) >> 10;

            // Scale back to byte offset (multiply by 8)
            return scaledImm12 << 3;
        }

        //*****************************************************************************
        //  Deposit the 12-bit page offset 'imm12' into an LDR instruction (unsigned immediate)
        //  For 64-bit LDR, the immediate represents offset/8 (scaled by 8 bytes)
        //*****************************************************************************
        private static unsafe void PutArm64Rel12Ldr(uint* pCode, int imm12)
        {
            // Verify that we got a valid offset and that it's aligned to 8 bytes
            Debug.Assert(FitsInRel12(imm12));
            Debug.Assert((imm12 & 7) == 0, "LDR offset must be 8-byte aligned");

            uint ldrInstr = *pCode;
            // Check ldr opcode: 0b11111001010000000000000000000000 (LDR 64-bit register, unsigned immediate)
            Debug.Assert((ldrInstr & 0xFFC00000) == 0xF9400000);

            // Scale the offset by dividing by 8 for the instruction encoding
            int scaledImm12 = imm12 >> 3;

            // 0xFFC003FF: Mask to preserve bits 31-22 (opcode) and bits 9-0 (registers)
            // Clear bits 21-10 which will hold the scaled immediate value
            ldrInstr &= 0xFFC003FF;
            ldrInstr |= (uint)(scaledImm12 << 10);     // Set bits 21-10 with scaled offset

            *pCode = ldrInstr;                         // write the assembled instruction

            Debug.Assert(GetArm64Rel12Ldr(pCode) == imm12);
        }

        private static unsafe int GetArm64Rel28(uint* pCode)
        {
            uint branchInstr = *pCode;

            // first shift 6 bits left to set the sign bit,
            // then arithmetic shift right by 4 bits
            int imm28 = (((int)(branchInstr & 0x03FFFFFF)) << 6) >> 4;

            return imm28;
        }

        private static bool FitsInArm64Rel28(long imm28)
        {
            return (imm28 >= -0x08000000L) && (imm28 < 0x08000000L);
        }

        private static unsafe void PutArm64Rel28(uint* pCode, long imm28)
        {
            // Verify that we got a valid offset
            Debug.Assert(FitsInArm64Rel28(imm28));

            Debug.Assert((imm28 & 0x3) == 0);    // the low two bits must be zero

            uint branchInstr = *pCode;

            branchInstr &= 0xFC000000;       // keep bits 31-26

            Debug.Assert((branchInstr & 0x7FFFFFFF) == 0x14000000);  // Must be B or BL

            // Assemble the pc-relative delta 'imm28' into the branch instruction
            branchInstr |= (uint)(((imm28 >> 2) & 0x03FFFFFFU));

            *pCode = branchInstr;          // write the assembled instruction

            Debug.Assert(GetArm64Rel28(pCode) == imm28);
        }

        private static unsafe int GetLoongArch64PC12(uint* pCode)
        {
            uint pcInstr = *pCode;

            // first get the high 20 bits,
            int imm = (int)(((pcInstr >> 5) & 0xFFFFF) << 12);

            // then get the low 12 bits,
            pcInstr = *(pCode + 1);
            imm |= (int)((pcInstr >> 10) & 0xFFF);

            return imm;
        }

        //  case:EA_HANDLE_CNS_RELOC
        //   pcalau12i  reg, off-hi-20bits
        //   addi_d  reg, reg, off-lo-12bits
        //  case:EA_PTR_DSP_RELOC
        //   pcalau12i  reg, off-hi-20bits
        //   ld_d  reg, reg, off-lo-12bits
        private static unsafe void PutLoongArch64PC12(uint* pCode, long imm)
        {
            // Verify that we got a valid offset
            Debug.Assert((int)imm == imm);

            uint pcInstr = *pCode;

            Debug.Assert((pcInstr & 0xFE000000) == 0x1a000000);  // Must be pcalau12i

            // Assemble the pc-relative high 20 bits of 'imm' into the pcalau12i instruction
            pcInstr |= (uint)((imm >> 7) & 0x1FFFFE0);

            *pCode = pcInstr;          // write the assembled instruction

            pcInstr = *(pCode + 1);

            // Assemble the pc-relative low 12 bits of 'imm' into the addid or ld instruction
            pcInstr |= (uint)((imm & 0xFFF) << 10);

            *(pCode + 1) = pcInstr;          // write the assembled instruction

            Debug.Assert(GetLoongArch64PC12(pCode) == imm);
        }

        private static unsafe long GetLoongArch64JIR(uint* pCode)
        {
            uint pcInstr = *pCode;

            // first get the high 20 bits,
            long imm = ((long)((pcInstr >> 5) & 0xFFFFF) << 18);

            // then get the low 18 bits
            pcInstr = *(pCode + 1);
            imm += ((long)((short)((pcInstr >> 10) & 0xFFFF))) << 2;

            return imm;
        }

        private static unsafe void PutLoongArch64JIR(uint* pCode, long imm38)
        {
            // Verify that we got a valid offset
            Debug.Assert((imm38 >= -0x2000000000L) && (imm38 < 0x2000000000L));

            Debug.Assert((imm38 & 0x3) == 0);    // the low two bits must be zero

            uint pcInstr = *pCode;

            Debug.Assert(pcInstr == 0x1e000010);  // Must be pcaddu18i t4, 0

            long relOff = imm38 & 0x20000;
            long imm = imm38 + relOff;
            relOff = (((imm & 0x1ffff) - relOff) >> 2) & 0xffff;

            // Assemble the pc-relative high 20 bits of 'imm38' into the pcaddu18i instruction
            pcInstr |= (uint)(((imm >> 18) & 0xFFFFF) << 5);

            *pCode = pcInstr;          // write the assembled instruction

            pcInstr = *(pCode + 1);

            // Assemble the pc-relative low 18 bits of 'imm38' into the jirl instruction
            pcInstr |= (uint)(relOff << 10);

            *(pCode + 1) = pcInstr;          // write the assembled instruction

            Debug.Assert(GetLoongArch64JIR(pCode) == imm38);
        }

        private static unsafe long GetRiscV64AuipcCombo(uint* pCode, bool isStype)
        {
            const uint OpcodeAuipc = 0x17;
            const uint OpcodeAddi = 0x13;
            const uint OpcodeLoad = 0x03;
            const uint OpcodeStore = 0x23;
            const uint OpcodeLoadFp = 0x07;
            const uint OpcodeStoreFp = 0x27;
            const uint OpcodeJalr = 0x67;
            const uint OpcodeMask = 0x7F;

            const uint Funct3AddiJalr = 0x0000;
            const uint Funct3Mask = 0x7000;

            uint auipc = pCode[0];
            Debug.Assert((auipc & OpcodeMask) == OpcodeAuipc);
            uint auipcRegDest = (auipc >> 7) & 0x1F;
            Debug.Assert(auipcRegDest != 0);

            long hi20 = (((int)auipc) >> 12) << 12;

            uint instr = pCode[1];
            uint opcode = instr & OpcodeMask;
            uint funct3 = instr & Funct3Mask;
            Debug.Assert(opcode == OpcodeLoad || opcode == OpcodeStore || opcode == OpcodeLoadFp || opcode == OpcodeStoreFp ||
                ((opcode == OpcodeAddi || opcode == OpcodeJalr) && funct3 == Funct3AddiJalr));
            Debug.Assert(isStype == (opcode == OpcodeStore || opcode == OpcodeStoreFp));
            uint addrReg = (instr >> 15) & 0x1F;
            Debug.Assert(auipcRegDest == addrReg);

            long lo12 = (((int)instr) >> 25) << 5; // top 7 bits are in the same spot
            int bottomBitsPos = isStype ? 7 : 20;
            lo12 |= (instr >> bottomBitsPos) & 0x1F;

            return hi20 + lo12;
        }

        // INS_OPTS_RELOC: placeholders.  2-ins:
        //  case:EA_HANDLE_CNS_RELOC
        //   auipc  reg, off-hi-20bits
        //   addi   reg, reg, off-lo-12bits
        //  case:EA_PTR_DSP_RELOC
        //   auipc  reg, off-hi-20bits
        //   ld     reg, reg, off-lo-12bits
        //  case:
        // INS_OPTS_C
        //   auipc  reg, off-hi-20bits
        //   jalr   reg, reg, off-lo-12bits
        private static unsafe void PutRiscV64AuipcCombo(uint* pCode, long offset, bool isStype)
        {
            int lo12 = (int)((offset << (64 - 12)) >> (64 - 12));
            int hi20 = (int)(offset - lo12);
            Debug.Assert((long)lo12 + (long)hi20 == offset);

            Debug.Assert(GetRiscV64AuipcCombo(pCode, isStype) == 0);
            pCode[0] |= (uint)hi20;
            int bottomBitsPos = isStype ? 7 : 20;
            pCode[1] |= (uint)((lo12 >> 5) << 25); // top 7 bits are in the same spot
            pCode[1] |= (uint)((lo12 & 0x1F) << bottomBitsPos);
            Debug.Assert(GetRiscV64AuipcCombo(pCode, isStype) == offset);
        }

        public Relocation(RelocType relocType, int offset, ISymbolNode target)
        {
            RelocType = relocType;
            Offset = offset;
            Target = target;
        }

        public static unsafe void WriteValue(RelocType relocType, void* location, long value)
        {
            switch (relocType)
            {
                case RelocType.IMAGE_REL_BASED_ABSOLUTE:
                case RelocType.IMAGE_REL_BASED_ADDR32NB:
                case RelocType.IMAGE_REL_BASED_HIGHLOW:
                case RelocType.IMAGE_REL_BASED_REL32:
                case RelocType.IMAGE_REL_BASED_RELPTR32:
                case RelocType.IMAGE_REL_SECREL:
                case RelocType.IMAGE_REL_TLSGD:
                case RelocType.IMAGE_REL_TPOFF:
                case RelocType.IMAGE_REL_SYMBOL_SIZE:
                case RelocType.IMAGE_REL_FILE_ABSOLUTE:
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_CALL:
                    *(int*)location = (int)value;
                    break;
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_LD64_LO12:
                    break;
                case RelocType.IMAGE_REL_BASED_DIR64:
                    *(long*)location = value;
                    break;
                case RelocType.IMAGE_REL_BASED_THUMB_MOV32:
                case RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL:
                    PutThumb2Mov32((ushort*)location, (uint)value);
                    break;
                case RelocType.IMAGE_REL_BASED_THUMB_BRANCH24:
                    PutThumb2BlRel24((ushort*)location, (int)value);
                    break;
                case RelocType.IMAGE_REL_BASED_ARM64_BRANCH26:
                    PutArm64Rel28((uint*)location, value);
                    break;
                case RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21:
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_ADR_PAGE21:
                    PutArm64Rel21((uint*)location, (int)value);
                    break;
                case RelocType.IMAGE_REL_ARM64_TLS_SECREL_HIGH12A:
                    PutArm64TlsRel12((uint*)location, (int)value);
                    break;
                case RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A:
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_ADD_LO12:
                case RelocType.IMAGE_REL_ARM64_TLS_SECREL_LOW12A:
                    PutArm64Rel12((uint*)location, (int)value);
                    break;
                case RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12L:
                    PutArm64Rel12Ldr((uint*)location, (int)value);
                    break;
                case RelocType.IMAGE_REL_BASED_LOONGARCH64_PC:
                    PutLoongArch64PC12((uint*)location, value);
                    break;
                case RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR:
                    PutLoongArch64JIR((uint*)location, value);
                    break;
                case RelocType.IMAGE_REL_BASED_RISCV64_CALL_PLT:
                case RelocType.IMAGE_REL_BASED_RISCV64_PCREL_I:
                case RelocType.IMAGE_REL_BASED_RISCV64_PCREL_S:
                    bool isStype = (relocType is RelocType.IMAGE_REL_BASED_RISCV64_PCREL_S);
                    PutRiscV64AuipcCombo((uint*)location, value, isStype);
                    break;
                default:
                    Debug.Fail("Invalid RelocType: " + relocType);
                    break;
            }
        }

        public static int GetSize(RelocType relocType)
        {
            return relocType switch
            {
                RelocType.IMAGE_REL_BASED_DIR64 => 8,
                RelocType.IMAGE_REL_BASED_HIGHLOW => 4,
                RelocType.IMAGE_REL_BASED_ADDR32NB => 4,
                RelocType.IMAGE_REL_BASED_REL32 => 4,
                RelocType.IMAGE_REL_BASED_RELPTR32 => 4,
                RelocType.IMAGE_REL_FILE_ABSOLUTE => 4,
                // The relocation itself aren't these sizes, but their values
                // are immediates in instructions within
                // a span of this many bytes.
                RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21 => 4,
                RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A => 4,
                RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12L => 4,
                RelocType.IMAGE_REL_BASED_THUMB_MOV32 => 8,
                RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL => 8,
                RelocType.IMAGE_REL_BASED_LOONGARCH64_PC => 8,
                RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR => 8,
                RelocType.IMAGE_REL_BASED_RISCV64_CALL_PLT => 8,
                RelocType.IMAGE_REL_BASED_RISCV64_PCREL_I => 8,
                RelocType.IMAGE_REL_BASED_RISCV64_PCREL_S => 8,
                _ => throw new NotSupportedException(),
            };
        }

        public static unsafe long ReadValue(RelocType relocType, void* location)
        {
            switch (relocType)
            {
                case RelocType.IMAGE_REL_BASED_ABSOLUTE:
                case RelocType.IMAGE_REL_BASED_ADDR32NB:
                case RelocType.IMAGE_REL_BASED_HIGHLOW:
                case RelocType.IMAGE_REL_BASED_REL32:
                case RelocType.IMAGE_REL_BASED_RELPTR32:
                case RelocType.IMAGE_REL_SECREL:
                case RelocType.IMAGE_REL_TLSGD:
                case RelocType.IMAGE_REL_TPOFF:
                case RelocType.IMAGE_REL_FILE_ABSOLUTE:
                case RelocType.IMAGE_REL_SYMBOL_SIZE:
                    return *(int*)location;
                case RelocType.IMAGE_REL_BASED_DIR64:
                    return *(long*)location;
                case RelocType.IMAGE_REL_BASED_THUMB_MOV32:
                case RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL:
                    return (long)GetThumb2Mov32((ushort*)location);
                case RelocType.IMAGE_REL_BASED_THUMB_BRANCH24:
                    return (long)GetThumb2BlRel24((ushort*)location);
                case RelocType.IMAGE_REL_BASED_ARM64_BRANCH26:
                    return (long)GetArm64Rel28((uint*)location);
                case RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21:
                    return GetArm64Rel21((uint*)location);
                case RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A:
                case RelocType.IMAGE_REL_ARM64_TLS_SECREL_HIGH12A:
                case RelocType.IMAGE_REL_ARM64_TLS_SECREL_LOW12A:
                    return GetArm64Rel12((uint*)location);
                case RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12L:
                    return GetArm64Rel12Ldr((uint*)location);
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_LD64_LO12:
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_ADD_LO12:
                case RelocType.IMAGE_REL_AARCH64_TLSLE_ADD_TPREL_HI12:
                case RelocType.IMAGE_REL_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
                    // TLS relocs do not have offsets
                    Debug.Assert((GetArm64Rel12((uint*)location) & 0xFF) == 0);
                    return 0;
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_ADR_PAGE21:
                    // TLS relocs do not have offsets
                    Debug.Assert((GetArm64Rel21((uint*)location) & 0xFF) == 0);
                    return 0;
                case RelocType.IMAGE_REL_AARCH64_TLSDESC_CALL:
                    // TLS relocs do not have offsets
                    return 0;
                case RelocType.IMAGE_REL_BASED_LOONGARCH64_PC:
                    return (long)GetLoongArch64PC12((uint*)location);
                case RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR:
                    return (long)GetLoongArch64JIR((uint*)location);
                case RelocType.IMAGE_REL_BASED_RISCV64_CALL_PLT:
                case RelocType.IMAGE_REL_BASED_RISCV64_PCREL_I:
                case RelocType.IMAGE_REL_BASED_RISCV64_PCREL_S:
                    bool isStype = (relocType is RelocType.IMAGE_REL_BASED_RISCV64_PCREL_S);
                    return GetRiscV64AuipcCombo((uint*)location, isStype);
                default:
                    Debug.Fail("Invalid RelocType: " + relocType);
                    return 0;
            }
        }

        /// <summary>
        /// Return file relocation type for the given relocation type. If the relocation
        /// doesn't require a file-level relocation entry in the .reloc section, 0 is returned
        /// corresponding to the IMAGE_REL_BASED_ABSOLUTE no-op relocation record.
        /// </summary>
        /// <param name="relocationType">Relocation type</param>
        /// <returns>File-level relocation type or 0 (IMAGE_REL_BASED_ABSOLUTE) if none is required</returns>
        public static RelocType GetFileRelocationType(RelocType relocationType)
        {
            switch (relocationType)
            {
                case RelocType.IMAGE_REL_BASED_HIGHLOW:
                case RelocType.IMAGE_REL_BASED_DIR64:
                case RelocType.IMAGE_REL_BASED_THUMB_MOV32:
                    return relocationType;

                default:
                    return RelocType.IMAGE_REL_BASED_ABSOLUTE;
            }
        }

        public override string ToString()
        {
            return $"{Target} ({RelocType}, 0x{Offset:X})";
        }
    }
}
